/*
 * Copyright (c) 2015-2017 Alex Spataru <alex_spataru@outlook.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <math.h>
#include "VirtualJoystick.h"

VirtualJoystick::VirtualJoystick(QObject *parent) : QObject(parent)
{
	m_axisRange = 1;
	m_joystickEnabled = false;
	m_joystick.blacklisted = false;
	m_joystick.name = tr("Virtual Joystick");

	/* Initialize POVs */
	m_joystick.povs.append(0);

	/* Initialize axes */
	m_axisStatus = QVector<AxisState>(NUMBER_OF_AXES, AxisState::STILL);
	m_axisValue = QVector<qint16>(NUMBER_OF_AXES, 0);
	for (int i = 0; i < NUMBER_OF_AXES; ++i) {
		m_joystick.axes.append(0);
	}

	/* Initialize buttons */
	for (int i = 0; i < NUMBER_OF_BUTTONS; ++i)
		m_joystick.buttons.append(false);

	m_timerUpdateAxis.reset(new QTimer());
	connect(m_timerUpdateAxis.get(), &QTimer::timeout, this,
		&VirtualJoystick::updateAxis);
}

/**
 * Returns the current axis range of the joystick.
 * The axis range is an absolute value that represents the maximum value that
 * the joystick can have.
 *
 * For example, the \c QJoystick system supports an axis range of 1 (-1 to 1).
 * If you set an axis range of 0.8 to the virtual joystick, then it will report
 * values ranging from -0.8 to 0.8.
 */
qreal VirtualJoystick::axisRange() const
{
	return m_axisRange;
}

/**
 * Returns \c true if the virtual joystick is enabled.
 */
bool VirtualJoystick::joystickEnabled() const
{
	return m_joystickEnabled;
}

/**
 * Returns a pointer to the virtual josytick device. This can be used if you
 * need to customize the virtual joystick (e.g. add new axes or buttons).
 */
QJoystickDevice *VirtualJoystick::joystick()
{
	return &m_joystick;
}

/**
 * Sets the ID of the virtual joystick device. The \c QJoysticks will
 * automatically change the \a ID of the virtual joystick when it scans for
 * new joysticks.
 *
 * The virtual joystick will ALWAYS be the last joystick to be registered.
 */
void VirtualJoystick::setJoystickID(int id)
{
	m_joystick.id = id;
}

/**
 * Changes the axis range that the joystick can use. For example, if you set
 * an axis range of 0.8, then axis values will be reported from -0.8 to 0.8.
 *
 * If you set an axis range of 1 (maximum), then the joystick will report axis
 * values ranging from -1 to 1.
 */
void VirtualJoystick::setAxisRange(qreal range)
{
	range = fabs(range);

	if (range > 1)
		range = 1;

	m_axisRange = range;
}

/**
 * Enables or disables the virtual joystick device.
 */
void VirtualJoystick::setJoystickEnabled(bool enabled)
{
	if (enabled) {
		// That means that the axes will be updated each 10 ms
		m_timerUpdateAxis->start(10);
		qApp->installEventFilter(this);
	}

	else {
		m_timerUpdateAxis->stop();
		// Removing the event filter since the joystick is no longer active
		qApp->removeEventFilter(this);
	}

	m_joystickEnabled = enabled;
	emit enabledChanged();
}

void VirtualJoystick::setAxisSensibility(qreal sensibility)
{
	if (sensibility > 1 || sensibility < 0) {
		qFatal("Fatal: VirtualJoystick Axis sensibility must be a value between 0 and 1");
	}

	const qint16 stepMinimum = 50;
	const qint16 stepMaximum = 1000;

	m_axisStep = static_cast<qint16>((-stepMaximum + stepMinimum) *
					 sensibility) +
		     stepMaximum;
}

/**
 * Polls the keyboard events and if required, reports a change in the axis
 * values of the virtual joystick device.
 */
void VirtualJoystick::readAxes(int key, bool pressed)
{

	/* Horizontal axis on thumb 1 */
	if (key == Qt::Key_A) {
		if (pressed)
			m_axisStatus[AXIS_AD] = DECREASE;
		else
			m_axisStatus[AXIS_AD] = STILL;
	} else if (key == Qt::Key_D) {
		if (pressed)
			m_axisStatus[AXIS_AD] = INCREASE;
		else
			m_axisStatus[AXIS_AD] = STILL;
	}

	/* Vertical axis on thumb 1 */
	if (key == Qt::Key_S) {
		if (pressed)
			m_axisStatus[AXIS_SW] = DECREASE;
		else
			m_axisStatus[AXIS_SW] = STILL;
	} else if (key == Qt::Key_W) {
		if (pressed)
			m_axisStatus[AXIS_SW] = INCREASE;
		else
			m_axisStatus[AXIS_SW] = STILL;
	}

	/* Trigger 1 */
	if (key == Qt::Key_Q) {
		if (pressed)
			m_axisStatus[AXIS_QE] = DECREASE;
		else
			m_axisStatus[AXIS_QE] = STILL;
	} else if (key == Qt::Key_E) {
		if (pressed)
			m_axisStatus[AXIS_QE] = INCREASE;
		else
			m_axisStatus[AXIS_QE] = STILL;
	}

	/* Trigger 2 */
	if (key == Qt::Key_U) {
		if (pressed)
			m_axisStatus[AXIS_UO] = DECREASE;
		else
			m_axisStatus[AXIS_UO] = STILL;
	} else if (key == Qt::Key_O) {
		if (pressed)
			m_axisStatus[AXIS_UO] = INCREASE;
		else
			m_axisStatus[AXIS_UO] = STILL;
	}

	/* Horizontal axis on thumb 2 */
	if (key == Qt::Key_J) {
		if (pressed)
			m_axisStatus[AXIS_JL] = DECREASE;
		else
			m_axisStatus[AXIS_JL] = STILL;
	} else if (key == Qt::Key_L) {
		if (pressed)
			m_axisStatus[AXIS_JL] = INCREASE;
		else
			m_axisStatus[AXIS_JL] = STILL;
	}

	/* Vertical axis on thumb 2 */
	if (key == Qt::Key_K) {
		if (pressed)
			m_axisStatus[AXIS_KI] = DECREASE;
		else
			m_axisStatus[AXIS_KI] = STILL;
	} else if (key == Qt::Key_I) {
		if (pressed)
			m_axisStatus[AXIS_KI] = INCREASE;
		else
			m_axisStatus[AXIS_KI] = STILL;
	}
}

void VirtualJoystick::updateAxis()
{
	for (quint8 i = 0; i < NUMBER_OF_AXES; i++) {
		changeAxisValue(i);
	}
}

/**
 * Polls the keyboard events and if required, reports a change in the POV/hat
 * values of the virtual joystick device.
 */
void VirtualJoystick::readPOVs(int key, bool pressed)
{
	int angle = 0;

	if (key == Qt::Key_Up)
		angle = 360;
	else if (key == Qt::Key_Right)
		angle = 90;
	else if (key == Qt::Key_Left)
		angle = 270;
	else if (key == Qt::Key_Down)
		angle = 180;

	if (!pressed)
		angle = 0;

	if (joystickEnabled()) {
		QJoystickPOVEvent event;
		event.pov = 0;
		event.angle = angle;
		event.joystick = joystick();

		emit povEvent(event);
	}
}

/**
 * Polls the keyboard events and if required, reports a change in the button
 * values of the virtual joystick device.
 */
void VirtualJoystick::readButtons(int key, bool pressed)
{
	int button = -1;

	if (key == Qt::Key_0) { // Special key that reset all axes
		button = 0;
		resetAllAxes();
	} else if (key == Qt::Key_1)
		button = 1;
	else if (key == Qt::Key_2)
		button = 2;
	else if (key == Qt::Key_3)
		button = 3;
	else if (key == Qt::Key_4)
		button = 4;
	else if (key == Qt::Key_5)
		button = 5;
	else if (key == Qt::Key_6)
		button = 6;
	else if (key == Qt::Key_7)
		button = 7;
	else if (key == Qt::Key_8)
		button = 8;
	else if (key == Qt::Key_9)
		button = 9;

	if (button != -1 && joystickEnabled()) {
		QJoystickButtonEvent event;
		event.button = button;
		event.pressed = pressed;
		event.joystick = joystick();

		emit buttonEvent(event);
	}
}

/**
 * Called when the event filter detects a keyboard event.
 *
 * This function prompts the joystick to update its axis, button and POV values
 * based on the keys that have been pressed or released.
 */
void VirtualJoystick::processKeyEvent(QKeyEvent *event, bool pressed)
{
	if (joystickEnabled()) {
		readPOVs(event->key(), pressed);
		readAxes(event->key(), pressed);
		readButtons(event->key(), pressed);
	}
}

/**
 * "Listens" for keyboard presses or releases while any window or widget of the
 * application is focused.
 *
 * \note This function may or may not detect keyboard events when the
 *       application is not focused. This depends on the operating system and
 *       the window manager that is being used.
 */
bool VirtualJoystick::eventFilter(QObject *object, QEvent *event)
{
	Q_UNUSED(object);

	switch (event->type()) {
	case QEvent::KeyPress:
		processKeyEvent(static_cast<QKeyEvent *>(event), true);
		break;
	case QEvent::KeyRelease:
		processKeyEvent(static_cast<QKeyEvent *>(event), false);
		break;
	default:
		break;
	}

	return false;
}

void VirtualJoystick::changeAxisValue(quint8 axis)
{
	switch (m_axisStatus[axis]) {
	case STILL:
		return;

	case INCREASE:
		if (m_axisValue[axis] >
		    AXIS_MAXIMUM_VIRTUAL_JOYSTICK - m_axisStep)
			m_axisValue[axis] = AXIS_MAXIMUM_VIRTUAL_JOYSTICK;
		else
			m_axisValue[axis] += m_axisStep;
		break;

	case DECREASE:
		if (m_axisValue[axis] <
		    AXIS_MINIMUM_VIRTUAL_JOYSTICK + m_axisStep)
			m_axisValue[axis] = AXIS_MINIMUM_VIRTUAL_JOYSTICK;
		else
			m_axisValue[axis] -= m_axisStep;
		break;
	}

	QJoystickAxisEvent event;

	event.axis = axis;
	event.value = m_axisRange * static_cast<qreal>(m_axisValue[axis]) /
		      AXIS_MAXIMUM_VIRTUAL_JOYSTICK;
	event.joystick = joystick();

	emit axisEvent(event);
}

void VirtualJoystick::resetAllAxes()
{
	for (quint8 i = 0; i < NUMBER_OF_AXES; i++) {
		m_axisValue[i] = 0;

		QJoystickAxisEvent event;

		event.axis = i;
		event.value = 0;
		event.joystick = joystick();

		emit axisEvent(event);
	}
}
